﻿namespace MvvmTools.NotifyPropertyChanged
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Diagnostics.Contracts;
    using System.Linq.Expressions;
    using JetBrains.Annotations;

    internal sealed class AccessorCache
    {
        private readonly Dictionary<AccessorKey, object> _accessorsByKey = new Dictionary<AccessorKey, object>();

        public Accessor<TParent, TProperty> GetAccessor<TParent, TProperty>([NotNull] Expression<Func<TParent, TProperty>> propertySelector)
        {
            Contract.Requires<ArgumentNullException>(propertySelector != null);

            var name = propertySelector.GetParameterName();
            var key = new AccessorKey(typeof(TParent), typeof(TProperty), name);
            object accessor;
            if (!_accessorsByKey.TryGetValue(key, out accessor))
            {
                _accessorsByKey[key] = accessor = new Accessor<TParent, TProperty>(name, propertySelector);
            }

            return (Accessor<TParent, TProperty>)accessor;
        }

        [DebuggerDisplay("{name} : {propertyType}")]
        private class AccessorKey : IEquatable<AccessorKey>
        {
            private static readonly IEqualityComparerEx<AccessorKey> EqualityComparer = MvvmTools.EqualityComparer<AccessorKey>
                .Depends(i => i._parentType)
                .Depends(i => i._propertyType)
                .Depends(i => i._name).Create();

            private readonly Type _parentType;
            private readonly Type _propertyType;
            private readonly string _name;

            public AccessorKey([NotNull] Type parentType, [NotNull] Type propertyType, [NotNull] string name)
            {
                Contract.Requires<ArgumentNullException>(parentType != null);
                Contract.Requires<ArgumentNullException>(propertyType != null);
                Contract.Requires<ArgumentNullException>(name != null);
                Contract.Requires<ArgumentNullException>(!string.IsNullOrEmpty(name));

                _parentType = parentType;
                _propertyType = propertyType;
                _name = name;
            }

            public static bool operator ==(AccessorKey left, AccessorKey right)
            {
                return Equals(left, right);
            }

            public static bool operator !=(AccessorKey left, AccessorKey right)
            {
                return !Equals(left, right);
            }

            public bool Equals(AccessorKey other)
            {
                return EqualityComparer.Equals(this, other);
            }

            public override bool Equals(object obj)
            {
                return EqualityComparer.Equals(this, obj);
            }

            public override int GetHashCode()
            {
                return EqualityComparer.GetHashCode(this);
            }
        }
    }    
}